package main

import (
	"Infinichat_mixed/back/mapper"
	"Infinichat_mixed/entity"
	"Infinichat_mixed/server/s_processPkg"
	"Infinichat_mixed/server/s_utils"
	"Infinichat_mixed/transfer"
	"context"
	"encoding/json"
	"fmt"
	_ "github.com/jinzhu/gorm/dialects/mysql" // 引入mysql驱动
	"github.com/redis/go-redis/v9"
	"io"
	"log"
	"net"
	"strconv"
	"time"
)

func init() {
	s_utils.InitConfig()
	s_utils.InitMYSQL()
	s_utils.InitRedis()
	s_utils.InitUserBloomFilter()
	s_utils.InitRabbitMQ()
	go s_processPkg.ProcessDelayedMessages()
	go s_processPkg.StartP2PMsgSync()
	go s_processPkg.StartHeartbeatChecker()
}

func main() {
	listen, err := net.Listen("tcp", "0.0.0.0:8889")
	if err != nil {
		panic(err)
	}
	for {
		fmt.Println("wait for client.")
		conn, err := listen.Accept()
		if err != nil {
			fmt.Println("Accept err:", err)
			return
		}
		go run(conn)
	}
	defer s_utils.DB.Close()

}

type Processor struct {
	Conn net.Conn
}

// 根据客户端发送消息种类的不同，决定调用哪个函数来处理
func (this *Processor) serverProcessMes(mes *entity.Message) (err error) {
	// 这样放在外面是错误的，因为每次进来的业务不同，可能会造成连接重用的问题
	//up := &clientProcessPkg.UserProcess{
	//	Conn: this.Conn,
	//}
	switch mes.Type {
	case entity.LoginMesType:
		up := &s_processPkg.UserProcess{
			Conn: this.Conn,
		}
		err = up.Login(mes)
	case entity.AskForUsersType:
		// 这个是根据昵称返回users
		var fu entity.FindUserByNameMes
		err = json.Unmarshal([]byte(mes.Data), &fu)
		if err != nil {
			fmt.Println("Unmarshal err:", err)
		}
		users := mapper.FindUserByNickName_fuzzy(fu.FindName)
		mes_ret := &entity.Message{}
		mes_ret.Type = entity.AskForUsersType
		data, err := json.Marshal(users)
		if err != nil {
			fmt.Println("Marshal err:", err)
		}
		mes_ret.Data = string(data)
		data, err = json.Marshal(mes_ret)
		if err != nil {
			fmt.Println("Marshal err:", err)
		}
		tf := &transfer.Transfer{this.Conn}
		err = tf.WriterPkg(data)
		if err != nil {
			fmt.Println("Writer err:", err)
		}
	case entity.IsFriendType:
		var ifm entity.IsFriendMes
		_ = json.Unmarshal([]byte(mes.Data), &ifm)
		flag := mapper.IsFriend(ifm.SourceId, ifm.TargetId)

		// 先用布隆过滤器判断TargetId是否有效
		bloomFlag := s_utils.UserBloomFilter.Test([]byte(fmt.Sprint(ifm.TargetId)))
		reponseMsg := &entity.Message{}
		reponseMsg.Type = entity.IsFriendType
		reponseData := &entity.IsFriendMes{}
		if flag == false && bloomFlag == true {
			// TargetID合法 不是好友 发起好友申请
			// 先将添加请求进入数据库
			err := mapper.AddFriend(ifm.SourceId, ifm.TargetId)
			if err != nil {
				fmt.Println("Add friend err:", err)
			}
			// 给目标推送共同好友
			mutualFriends, _ := s_processPkg.GetMutualFriends(ifm.SourceId, ifm.TargetId)
			reponseData.MutualFriend = mutualFriends
			reponseData.Flag = true
		} else if flag == true {
			// 对方已是好友
			reponseData.Flag = false
			reponseData.Msg = string("对方已是你的好友")
		} else if bloomFlag == false {
			// 无效的好友ID
			reponseData.Flag = false
			reponseData.Msg = string("无效的好友ID")
		}

		// 好友添加回馈
		rd, _ := json.Marshal(reponseData)
		reponseMsg.Data = string(rd)
		res, _ := json.Marshal(reponseMsg)
		tf := &transfer.Transfer{this.Conn}
		_ = tf.WriterPkg(res)

		// 判断targetId是否在线 如果在线 则需要实时通知
		tag_conn := s_processPkg.GetConnById(ifm.TargetId)
		if tag_conn != nil {
			// 在线
			var mes_ret entity.Message
			mes_ret.Type = entity.NotifyUserAddType
			var n_ua = &entity.NotifyUserAdd{
				SourceId: ifm.SourceId,
				TargetId: ifm.TargetId,
			}
			data, err := json.Marshal(n_ua)
			if err != nil {
				fmt.Println("Marshal err:", err)
			}
			mes_ret.Data = string(data)
			tf2 := &transfer.Transfer{tag_conn}
			data, err = json.Marshal(mes_ret)
			err = tf2.WriterPkg(data)
			if err != nil {
				fmt.Println("Marshal err:", err)
			}
		}
	case entity.FindFriedAddType:
		tId, err := strconv.Atoi(mes.Data)
		friends, err := mapper.FindFriendReq(uint(tId))
		if err != nil {
			fmt.Println("Find friend err:", err)
		}
		data, err := json.Marshal(friends)
		if err != nil {
			fmt.Println("Marshal err:", err)
		}
		mes.Type = entity.FindFriedAddType
		mes.Data = string(data)
		data, err = json.Marshal(mes)
		if err != nil {
			fmt.Println("Marshal err:", err)
		}
		tf := &transfer.Transfer{this.Conn}
		err = tf.WriterPkg(data)
		if err != nil {
			fmt.Println("Writer err:", err)
		}
	case entity.Select2AddType:
		tmp := entity.IsFriendMes{}
		err := json.Unmarshal([]byte(mes.Data), &tmp)
		if err != nil {
			fmt.Println("Unmarshal err:", err)
		}
		// mysql-redis双写一致性：用户添加好友，先删除redis，再更新mysql，延迟一段时间后再更新redis
		if tmp.Flag == true {
			//conn := s_utils.RedisPool.Get()
			//defer conn.Close()
			//conn.Do("DEL", fmt.Sprintf("friends:%d", tmp.SourceId))
			//conn.Do("DEL", fmt.Sprintf("friends:%d", tmp.TargetId))
			ctx := context.Background()
			_, _ = s_utils.RedisClient.Del(ctx, fmt.Sprintf("friends:%d", tmp.SourceId)).Result()
			_, _ = s_utils.RedisClient.Del(ctx, fmt.Sprintf("friends:%d", tmp.TargetId)).Result()
		}

		err = mapper.ProcessAdd(tmp.SourceId, tmp.TargetId, tmp.Flag)
		is_success := 0
		if err != nil {
			fmt.Println("Add err:", err)
		} else {
			is_success = 1
			go func() {
				time.Sleep(2 * time.Second)
				retSids, _ := s_processPkg.LoadFriedsFromMysql(tmp.SourceId)
				_ = s_processPkg.AddFriends2Redis(tmp.SourceId, retSids)
				retTids, _ := s_processPkg.LoadFriedsFromMysql(tmp.TargetId)
				_ = s_processPkg.AddFriends2Redis(tmp.TargetId, retTids)
			}()
		}
		// 这里没考虑拒绝的情况哦~不是重点，不予理会
		// 这里需要检查好友是否在线，在线的话需要推送双方的好友状态
		fdData := entity.FriendAdd{uint8(is_success), false}
		SourceConn := s_processPkg.GetConnById(tmp.SourceId) // 这里应该是添加来源，所以好友ID应该是SourceId
		if SourceConn != nil {
			// 对方在线
			fdData.FirendOnline = true
			// 需要给对方推送好友通过添加请求
			var mesNotify entity.Message
			mesNotify.Type = entity.FriendAddType
			var mnData = make(map[string]interface{}, 0)
			mnData["FriendId"] = tmp.TargetId
			targetNotify, _ := json.Marshal(mnData)
			mesNotify.Data = string(targetNotify)
			stf := &transfer.Transfer{SourceConn}
			sfData, _ := json.Marshal(mesNotify)
			_ = stf.WriterPkg(sfData)
		}
		mesData, _ := json.Marshal(fdData)
		mes.Data = string(mesData)
		data, err := json.Marshal(mes)
		if err != nil {
			fmt.Println("Marshal err:", err)
		}
		tf := &transfer.Transfer{this.Conn}
		err = tf.WriterPkg(data)
		if err != nil {
			fmt.Println("Writer err:", err)
		}
	case entity.FindFriendType:
		tmp := entity.FindFriendAddMes{}
		err := json.Unmarshal([]byte(mes.Data), &tmp)
		if err != nil {
			fmt.Println("Unmarshal err:", err)
		}
		fid := tmp.SourceId
		users, err := mapper.FindFriendByFid(fid)
		if err != nil {
			fmt.Println("Find friend err:", err)
		}
		tmp.Friends = users
		data, err := json.Marshal(tmp)
		if err != nil {
			fmt.Println("Marshal err:", err)
		}
		mes.Data = string(data)
		data, err = json.Marshal(mes)
		if err != nil {
			fmt.Println("Marshal err:", err)
		}
		tf := &transfer.Transfer{this.Conn}
		err = tf.WriterPkg(data)
		if err != nil {
			fmt.Println("Writer err:", err)
		}
	case entity.SmsMesType:
		smsProcess := &s_processPkg.SmsProcess{}
		//fmt.Println("mes:", mes)
		smsProcess.SendBroadcastMes(mes)
	case entity.SmsMes2OneType:
		smsProcess := &s_processPkg.SmsProcess{}
		smsProcess.SendMes2One(mes)
	case entity.LogoutType:
		//fmt.Println("logout msg:", mes)
		// 从mes中取出id
		var logoutMes entity.LogoutMes
		json.Unmarshal([]byte(mes.Data), &logoutMes)
		up := &s_processPkg.UserProcess{
			Conn: this.Conn,
		}
		up.Logout(logoutMes.Id)
	case entity.CreateGroupType:
		g := &entity.Group{}
		err := json.Unmarshal([]byte(mes.Data), &g) // &g !!!!!!!
		if err != nil {
			fmt.Println("Unmarshal err:", err)
		}
		members2Add := g.Members
		g.Members = []uint{g.OwnerUser} // 群刚创建成员应该只有群主一个人
		err = mapper.CreateGroup(g)
		if err != nil {
			fmt.Println("Create err:", err)
		}
		// 群聊创建成功后 通知群主邀请的群聊成员
		for _, v := range members2Add {
			gi := entity.GroupInvite{
				MemberId:  v,
				GroupId:   g.Model.ID,
				IsRefused: 0,
				OwnerUser: g.OwnerUser,
			}
			err = mapper.GroupInvite(&gi)
			if err != nil {
				fmt.Println("GroupInvite err:", err)
			}
			// 查询所邀请的用户是否在线 如果在线 则需要实时推送 如果不在线 则需要在其登录后推送
			tag_conn := s_processPkg.GetConnById(v)
			if tag_conn != nil {
				// 在线
				var mes_ret entity.Message
				mes_ret.Type = entity.NotifyGroupAddType
				data, err := json.Marshal(gi)
				if err != nil {
					fmt.Println("Marshal err:", err)
				}
				mes_ret.Data = string(data)
				tf2 := &transfer.Transfer{tag_conn}
				data, err = json.Marshal(mes_ret)
				err = tf2.WriterPkg(data)
				if err != nil {
					fmt.Println("Marshal err:", err)
				}
			}
		}
	case entity.FindGIType:
		getMesData := make(map[string]interface{})
		_ = json.Unmarshal([]byte(mes.Data), &getMesData)
		gs, err := mapper.FindGIbyId(uint(getMesData["MemberId"].(float64)))
		if err != nil {
			fmt.Println("Find friend err:", err)
		}
		data, err := json.Marshal(gs)
		if err != nil {
			fmt.Println("Marshal err:", err)
		}
		mes.Data = string(data)
		data, err = json.Marshal(mes)
		if err != nil {
			fmt.Println("Marshal err:", err)
		}
		tf := &transfer.Transfer{this.Conn}
		err = tf.WriterPkg(data)
		if err != nil {
			fmt.Println("Writer err:", err)
		}
	case entity.AcceptGIType:
		gi := &entity.GroupInvite{}
		err := json.Unmarshal([]byte(mes.Data), &gi)
		if err != nil {
			fmt.Println("Unmarshal err:", err)
		}
		err = mapper.AddMember2Group(gi.GroupId, gi.MemberId, gi.IsRefused)
		if err != nil {
			fmt.Println("Add err:", err)
		}
	case entity.FindGroupType:
		var uid uint
		err := json.Unmarshal([]byte(mes.Data), &uid)
		if err != nil {
			fmt.Println("Unmarshal err:", err)
		}
		user := mapper.FindUserByID(uid)
		g_ids := user.Group_id
		data, err := json.Marshal(g_ids)
		if err != nil {
			fmt.Println("Marshal err:", err)
		}
		mes.Data = string(data)
		data, err = json.Marshal(mes)
		if err != nil {
			fmt.Println("Marshal err:", err)
		}
		tf := &transfer.Transfer{this.Conn}
		err = tf.WriterPkg(data)
		if err != nil {
			fmt.Println("Writer err:", err)
		}
	case entity.SmsMes2GroupType:
		var smg *entity.SmsMes2Group
		err := json.Unmarshal([]byte(mes.Data), &smg)
		if err != nil {
			fmt.Println("Unmarshal err:", err)
		}
		smsProcess := &s_processPkg.SmsProcess{}
		smsProcess.SendMes2Group(mes)
	case entity.NoteGmReadType:
		gm := entity.GroupMsg{}
		uid, _ := s_processPkg.GetUserIdByConn(this.Conn)
		_ = json.Unmarshal([]byte(mes.Data), &gm)
		_ = mapper.NoteGmRead(uid, &gm)
	case entity.NoteP2PReadType:
		P2pMsg := []entity.P2PMsg{}
		_ = json.Unmarshal([]byte(mes.Data), &P2pMsg)
		// 先查看是否在redis中，如果是在redis中且还持久化到数据库，更新redis中的相应数据的is_read为true
		uuid2Update := []string{}
		for _, msg := range P2pMsg {
			MsgZsetKey := entity.GetMsgZsetKey(msg.SendId, msg.ReceiveId)
			SyncSetKey := entity.GetSyncSetKey(msg.SendId, msg.ReceiveId)
			// 删除原来旧的版本
			oldJson, _ := json.Marshal(msg)
			_, _ = s_utils.RedisClient.ZRem(s_utils.Ctx, MsgZsetKey, oldJson).Result()
			msg.IsRead = true
			//redisConn := s_utils.RedisPool.Get()
			//defer redisConn.Close()
			//exists, _ := redis.Bool(redisConn.Do("SISMEMBER", SyncSetKey, msg.ID))
			ctx := context.Background()
			exists, _ := s_utils.RedisClient.SIsMember(ctx, SyncSetKey, msg.ID).Result()
			msgJson, err := json.Marshal(msg)
			if err != nil {
				log.Printf("JSON marshal error: %v", err)
				continue
			}

			// 如果msg.id不在SyncSetKey中，则说明还没持久化到数据库，更新redis上的MsgZsetKey中对应的数据即可
			// 无论是哪个分支，都需要更新redis
			//_, _ = redisConn.Do("ZADD", MsgZsetKey, msg.SendTime.Unix(), msgJson)
			_, _ = s_utils.RedisClient.ZAdd(s_utils.Ctx, MsgZsetKey, redis.Z{
				Score:  float64(msg.SendTime.Unix()),
				Member: msgJson,
			}).Result()
			if exists {
				// 否则，不仅要更新redis上的MsgZsetKey中对应的数据，还要更新MySQL的
				// 这里其实也可以用个守护线程去做脏页刷盘处理，但实现途径差不多，没必要
				uuid2Update = append(uuid2Update, msg.Uuid)
			}
		}
		_ = mapper.NoteP2PRead(uuid2Update)
	case entity.TimeSendType:
		var TimeSend entity.TimeSend
		_ = json.Unmarshal([]byte(mes.Data), &TimeSend)
		_ = s_processPkg.PushDelayedMessage(TimeSend)
	case entity.ChattingRecType:
		mesMp := make(map[string]interface{})
		_ = json.Unmarshal([]byte(mes.Data), &mesMp)
		sid, tid := uint(mesMp["sId"].(float64)), uint(mesMp["tId"].(float64))
		retMsgs := s_processPkg.GetP2PMsg(sid, tid)
		mesData, _ := json.Marshal(retMsgs)
		mes.Data = string(mesData)
		data, _ := json.Marshal(mes)
		tf := &transfer.Transfer{this.Conn}
		_ = tf.WriterPkg(data)
	case entity.HeartBeatType:
		id, _ := s_processPkg.GetUserIdByConn(this.Conn)
		fmt.Println("PING! id = ", id)
		if Ch, ok := s_processPkg.HeartBeatMp[id]; ok {
			Ch <- true
		}
	}
	return
}

func (this *Processor) process() (err error) {
	for {
		// 循环读取客户端发送的消息
		tf := &transfer.Transfer{
			Conn: this.Conn,
		}
		mes, err := tf.ReadPkg()
		if err != nil {
			if err == io.EOF {
				fmt.Println("client close.")
				return err
			} else {
				fmt.Println("", err)
				return err
			}
		}
		// 有请求进来了，重新设置最后活跃时间
		id, _ := s_processPkg.GetUserIdByConn(this.Conn)
		if id != 0 {
			// id为0的是临时请求的
			s_processPkg.SetLastActive(id)
		}
		err = this.serverProcessMes(mes)
		if err != nil {
			fmt.Println("serverProcessMes error:", err)
			return err
		}
	}
}

func run(conn net.Conn) {
	defer conn.Close()
	processor := &Processor{conn}
	err := processor.process()
	if err != nil {
		fmt.Println("serverProcessPkg err:", err)
		// 异常断开的情况，强制下线
		// 根据这个被断掉的process 去查询对应的id
		id, err_find := s_processPkg.GetUserIdByConn(conn)
		if err_find == nil && id != 0 {
			// 连接还在，异常退出的情况
			fmt.Println("exception disconnect id:", id)
			up := &s_processPkg.UserProcess{
				Conn: conn,
			}
			up.Logout(id)
		}
	}
}
